fix(core): re-inject nested instructions after compaction - #43723
Merged
Conversation
kitlangton
force-pushed
the
fix-nested-instruction-reinjection
branch
from
August 20, 2026 21:16
872510e to
45e1bfb
Compare
— AI code review (automated) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Nested AGENTS.md instructions silently vanish after compaction and never come back for the rest of the process lifetime. This fixes the dedup ledger so the next read in the affected subtree re-injects them.
Root and global AGENTS.md are unaffected — they live in the instruction epoch machinery, which survives compaction by design (
advanceEpochfolds current values into the new baseline). This bug is specific to nested AGENTS.md discovered by the read tool and injected as durable synthetic messages.Before / After
Before:
sub/deep/file.txt;SessionInstructions.loaddiscoverssub/AGENTS.md, injects it as a synthetic message, and records the path in two dedup layers: an in-memoryRefclaim and the synthetic's own metadata ledger.SessionHistory.loadtruncates model-visible history at the compaction boundary (gte(seq, compaction.seq), history.ts:43) — the synthetic carrying the rules is gone. The summarizer saw the rules as[Synthetic context]:prose, but nothing obligates the summary to retain instruction bytes.sub/AGENTS.md, but the in-memory claim — which nothing ever clears — still holds the session/path pair, soloadreturns before the durable ledger (which correctly forgot) is even consulted. No re-injection.sub/AGENTS.mduntil the Location layer restarts. Long-running servers — the exact population that compacts — are hit hardest.After:
Invariant after the fix: the synthetic message metadata in durable, model-visible history is the sole lasting dedup ledger. Anything that drops a synthetic from that history (compaction, committed revert) self-heals on the next read in the subtree.
How
packages/core/src/session/instructions.ts— theRefclaim (injected→ renamedinFlight) is released inEffect.ensuringafter the publish settles. Release is safe because the durable publish commits the synthetic and its metadata ledger atomically with the event (Bus commit hook), so a subsequent read scans the committed ledger and dedups there. The same-step parallel-read guard is preserved: concurrent loads for the same session/path still collapse to one injection.packages/core/test/session-instructions.test.ts— new test "re-injects nested instructions dropped from history by compaction": read → assert injected → publishCompaction.Started/Ended(projector creates the completed compaction, truncating history) → assert the synthetic is gone from model-visible history → read again → assert re-injection. Fails onv2withReceived length: 0.sequenceDiagram participant R as read tool participant SI as SessionInstructions participant Ref as in-memory claim participant H as durable history R->>SI: load(session, sub/AGENTS.md) SI->>Ref: claim (in-flight) SI->>H: scan metadata ledger — not found SI->>H: publish synthetic (rules + ledger, atomic) SI->>Ref: release claim Note over H: compaction truncates history — synthetic dropped R->>SI: load(session, sub/AGENTS.md) SI->>Ref: claim (empty — was released) SI->>H: scan ledger — forgotten with the synthetic SI->>H: re-inject rulesScope
Deliberately narrow. The structural fix — migrating nested AGENTS.md onto the instruction epoch machinery so nested rules survive compaction in the epoch baseline and gain change/removal narration — is a separate design (session-scoped instruction sources). This PR makes the current mechanism self-healing; a beat of staleness remains between compaction and the next read in the subtree, which the epoch migration would eliminate.
Testing
bun run test test/session-instructions.test.ts(packages/core): 7 pass / 0 fail; the new test fails without the fix (Received length: 0at the re-injection assertion)bun run test(packages/core): 1,915 tests, 0 failbun typecheck(packages/core): clean